import { Canvas, PPM } from './Canvas';
import { StudFigure } from './StudFigure';
import { FigureBase } from './FigureBase';
import { Wire } from '../model/conn/Wire';
import { Usage } from '../model/StudUsage';

const SnapStepWire = 1
const PathAcitveOpacity = 1
const PathUnacitveOpacity = 0.7

// 线段方向
enum SegDir {
    x = 'x',
    y = 'y', 
    none = 'none'
}

interface Coords {
    x: number, y: number
}

const WireDefaultColors: {[key:string]:string} = {}
WireDefaultColors[Usage.vcc] = '#FF0000'
WireDefaultColors[Usage.vdd] = '#FF0000'
WireDefaultColors[Usage.ground] = '#FF0000'
WireDefaultColors[Usage.uartmosi] = '#2D8CF0'
WireDefaultColors[Usage.uartmiso] = '#00CC00'
WireDefaultColors[Usage.spimosi] = '#2D8CF0'
WireDefaultColors[Usage.spimiso] = '#00CC00'
WireDefaultColors[Usage.spisck] = '#FFFF00'
WireDefaultColors[Usage.spics] = '#EEEEEE'
WireDefaultColors[Usage.i2cscl] = '#FFFF00'
WireDefaultColors[Usage.i2csda] = '#2D8CF0'

// segment 是一个表示path线段的双向链表
class Segment {

    // 起点坐标
    public x:number = 0
    public y: number = 0
    public none: number = -1  // 无实际意义，用于避免 segment[segment.dir] 时 segment.dir=='none' 的情况
    public next: Segment | undefined
    public prev: Segment | undefined

    // 线段的垂直方向
    // 线段只能向其垂直方向拖动
    public cross: SegDir

    constructor(
        coords: Coords ,
        public dir: SegDir = SegDir.none
    ) {
        this.setStart(coords)
        this.cross = this.crossOf(dir)
    }

    setStart(coords: Coords) {
        this.x = coords.x
        this.y = coords.y
    }

    crossOf(dir: SegDir) {
        switch(dir) {
            case SegDir.none :
                return SegDir.none
            case SegDir.x:
                return SegDir.y
            case SegDir.y :
                return SegDir.x
        }
    }

    length() {
        if(this.dir==SegDir.none || !this.next)
            return 0
        return Math.abs( this.next[this.dir] - this[this.dir] )
    }

    // 在path尾部延伸线段
    extend(nextCoords: Coords, dir: SegDir) {
        if( this.dir!=SegDir.none || this.cross!=SegDir.none )
            debugger
        this.dir = dir
        this.cross = this.crossOf(dir)
        this.next = new Segment(nextCoords)
        this.next.prev = this
        return this.next
    }

    toString(isHead=false): string {
        let str = `${isHead?'M':'L'}${this.x},${this.y}`
        if(this.next) 
            str+= this.next.toString(false)
        return str
    }

    // 计算点击 击中 path 上的那一段线段
    hit(coords: Coords, range: number): Segment|undefined {
        if(!this.next || this.dir==SegDir.none || this.cross==SegDir.none)
            return undefined
        
        if(
            // 线段方向 的范围内
            coords[this.dir] < Math.max( this.next[this.dir], this[this.dir])
            && coords[this.dir] > Math.min( this.next[this.dir], this[this.dir])
            // 线段垂直方向 的范围内
            && coords[this.cross] < (this[this.cross] + range)
            && coords[this.cross] > (this[this.cross] - range)
        ) {
            return this
        }

        return this.next.hit(coords, range)
    }

    destroy(fullLink=false) {
        ;(this.prev as any) = null
        if(fullLink && this.next) {
            this.next.destroy(true)
        }
        ;(this.next as any) = null
    }
}

export class WireFigure extends FigureBase {

    private path: raphaeljs.Element
    private glow: raphaeljs.Element
    public pathSet: raphaeljs.Element   // path + glow
    public shadow: raphaeljs.Element    // 用于加宽 path 的选择范围

    public from: StudFigure
    public to: StudFigure

    // 表示 path 线段的单向链表
    private head: Segment | undefined
    private tail: Segment | undefined

    get color(): string {
        return this._color
    }
    set color(value:string) {
        this._color = value
        this.path.attr({stroke: value})
    }

    constructor(public canvas: Canvas, public wire: Wire, private _color="#2d8cf0") {
        super("wire", wire)

        // 根据 usage 自动设置 wire 的颜色
        // if(!this._color) {
        //     if(wire.bus) {
        //         let usage = wire.bus.usage
        //         if(WireDefaultColors[usage]) {
        //             this._color = WireDefaultColors[usage]
        //         }
        //     }
        // }

        if(!wire.figure) {
            wire.figure = this
        }
        this.from = wire.from.figure as StudFigure
        this.to = wire.to.figure as StudFigure
        if(!this.from) debugger
        if(!this.to) debugger

        this.path = canvas.paper.path()
            .attr({
                stroke: this._color ,
                "stroke-width": 1 ,
                "stroke-linejoin": "round" ,
                "stroke-linecap": "round"
            })
        this.pathSet = this.path.glow({
            width: 4 ,
            color: "#444"
        })
        this.glow = (this.pathSet as any)[1]
        this.glow.hide()

        this.shadow = canvas.paper.path().attr({
                            "stroke-width": 10 ,
                            "stroke-opacity": 0.01
                        })

        this.route()

        this.repaint()

        this.from.partFigure.wires.push(this)
        this.to.partFigure.wires.push(this)

        let draggingSegment: Segment|undefined = undefined
        let dragOriginValue = -1
        this.shadow.drag(
            (dx:number, dy:number)=>{

                if(!draggingSegment || draggingSegment.cross==SegDir.none || !draggingSegment.next)
                    return

                let snap = SnapStepWire * PPM

                dx = dx/this.canvas.paper.viewbox.scale
                dx = Math.round(dx/snap) * snap

                dy = dy/this.canvas.paper.viewbox.scale
                dy = Math.round(dy/snap) * snap

                // 如果实际发生了移动，则取消 select 操作
                this.selector.dragging(dx, dy)
                    
                let detalCoords = {x: dx, y: dy}

                draggingSegment[draggingSegment.cross] = dragOriginValue + detalCoords[draggingSegment.cross]
                draggingSegment.next[draggingSegment.cross] = dragOriginValue + detalCoords[draggingSegment.cross]

                // 增加线段
                // 头部
                if(draggingSegment==this.head) {
                    this.head = new Segment(this.from.portPosition(), draggingSegment.cross)
                    draggingSegment.prev = this.head
                    this.head.next = draggingSegment
                }

                // 尾部
                if(draggingSegment.next==this.tail) {
                    this.tail = draggingSegment.next.extend(this.to.portPosition(), draggingSegment.cross)
                }

                this.repaint()
                
            } ,
            // start
            (x:number, y:number, e: any) => {

                draggingSegment = undefined

                if(!this.head)
                    return false

                let coords = this.canvas.coords(e.clientX, e.clientY)

                draggingSegment = this.head.hit(coords, 5*PPM/this.canvas.paper.viewbox.scale)
                
                // 无法确定击中线段, 或 hit 返回无效的segment
                if(!draggingSegment || draggingSegment.cross==SegDir.none) {
                    return false
                }

                dragOriginValue = coords[draggingSegment.cross]
            } ,
            // end
            () => {
                if(draggingSegment){
                    this.mergeTinySegments()
                }
                draggingSegment = undefined
                dragOriginValue = -1
            }
        )

        this.on("active", ()=>{
            this.glow.show()
            this.path.attr('stroke-opacity', PathAcitveOpacity)
        })
        this.on("unactive", ()=>{
            this.glow && this.glow.hide()
            this.path.attr('stroke-opacity', PathUnacitveOpacity)
        })

        this.selector.setup(this.shadow)
    }

    
    // 合并短小的的线段
    public mergeTinySegments() {
        if(!this.head || !this.tail) {
            debugger
            return
        }

        let threshold = (SnapStepWire+0.1) * PPM
        let needRepaint = false

        for(let seg:Segment|undefined = this.head ;
            seg && seg!=this.tail ;
            seg=seg?.next
        ) {
            if(seg.length()>threshold ) {
                continue
            }
            
            // seg 的下一段 没有连接到 tail ，可以自由活动
            if( seg.next && seg.next.next && seg.next.next!=this.tail ) {

                /*
                seg 本身为 head , 无法删除
                删除 seg.next ， 移动 seg.next.next, 改变 seg 方向

                               |
                 seg.next (b)  |
                     +---------+ seg.next.next (c)
                     o
                  seg (a)
                  
                */
                if(seg==this.head || !seg.prev) {
                    let a = seg
                    let b = seg.next
                    let c = seg.next.next
                    a.next = c
                    c.prev = a
                    a.dir = b.dir
                    a.cross = b.cross
                    c[c.dir] = a[c.dir]
                    b.prev = b.next = undefined
                    needRepaint = true
                }

                /*
                删除 seg 和 seg.next 线段，移动 seg.next.next
                从 seg.prev 继续遍历
                
                      seg.next (c)     |
                             +---------+ seg.next.next (d)
                      *------+ seg (b)
                seg.prev (a)
                
                */
                else {
                    let a: Segment|undefined = seg.prev
                    let b = seg
                    let c = seg.next
                    let d = seg.next.next
                    a.next = d
                    d.prev = a
                    d[b.dir] = a[d.dir]
                    c.next = c.prev = undefined
                    b.next = b.prev = undefined
                    needRepaint = true
                    seg = a
                }
            }

            // seg.prev 不是 head，可以自由活动
            else if( seg.prev && seg.prev!=this.head ) {

                /*
                seg 连接到了尾部 (seg.next==this.tail)
                删除 seg， 移动 seg.prev

                             seg.next (c)
                seg.prev (a)     o
                      +----------+ 
                      |         seg (b)
                      |

                */
                if( seg.next==this.tail ) {
                    // debugger
                    let a = seg.prev 
                    let b = seg
                    let c: Segment | undefined = seg.next
                    a.next = c
                    c.prev = a
                    a[a.cross] = c[a.cross]
                    b.prev = b.next = undefined

                    needRepaint = true
                    seg = c
                }

                /*
                seg 没有连接到尾部，删除 seg (b) 和 seg.next (c)
                移动 seg.prev (a) 从 seg.prev (a) 向后继续遍历

                              seg.next (c)  
                seg.prev (a)     +---------* seg.next.next (d)  
                     +-----------+ seg (b)
                     |
                     |

                */
                else if(seg.next!=this.tail && seg.next && seg.next.next){
                    let a: Segment|undefined = seg.prev 
                    let b = seg
                    let c = seg.next
                    let d = seg.next.next

                    a.next = d
                    d.prev = a
                    a[a.cross] = d[a.cross]
                    b.prev = b.next = undefined
                    c.prev = c.next = undefined
                    seg = a
                    needRepaint = true
                }
            }

        }

        if(needRepaint)
            this.repaint()
    }


    public update() {
        if( !this.head || !this.tail ){
            return
        }
        
        // 零件内部stud之间的 wire
        // 整体移动
        if( this.from.partFigure == this.to.partFigure ) {
            let fromPos = this.from.portPosition()
            let dx = fromPos.x - this.head.x
            let dy = fromPos.y - this.head.y

            for(let seg: Segment|undefined=this.head; seg; seg=seg.next) {
                seg.x+= dx
                seg.y+= dy
            }
        }

        else {
            // 更新首尾两端的位置
            this.head.setStart(this.from.portPosition())
            this.tail.setStart(this.to.portPosition())
    
            // 更新两端的相邻点
            if( this.head.next && this.head.next != this.tail ){
                this.head.next[this.head.cross] = this.head[this.head.cross]
            }
            if( this.tail.prev && this.tail.prev != this.head ){
                this.tail.prev[this.tail.prev.cross] = this.tail[this.tail.prev.cross]

            }
        }

        this.repaint()
    }

    private follow(a:Segment, b:Segment, axis: 'x'|'y'|'none') {
        if( axis!='none')
            a[axis] = b[axis]
    }

    public repaint() {
        if(!this.head)
            return

        let path = this.head.toString(true)

        this.path.attr('path', path)
        this.path.attr({
            'stroke-opacity': this.actived? PathAcitveOpacity: PathUnacitveOpacity ,
            'stroke-width': this.actived? 1.5: 1.5 ,
        })

        this.glow.attr('path', path)
        this.shadow.attr('path', path)
    }

    public route() {
        let fromPos = this.from.portPosition()
        let toPos = this.to.portPosition()

        // 起点
        this.head = new Segment(fromPos)

        // 插入中间点
        let x = fromPos.x + (toPos.x-fromPos.x)/2
        this.tail = this.head.extend({x, y: fromPos.y}, SegDir.x)
        this.tail = this.tail.extend({x, y: toPos.y}, SegDir.y)
        
        // 终点
        this.tail = this.tail.extend(toPos, SegDir.x)
    }

    public remove() {

        this.head && this.head.destroy(true)
        delete this.head
        delete this.tail

        this.from.partFigure.removeWire(this)
        // @ts-ignore
        delete this.from

        this.to.partFigure.removeWire(this)
        // @ts-ignore
        delete this.to

        this.path.remove()
        // @ts-ignore
        delete this.path

        this.glow.remove()
        // @ts-ignore
        delete this.glow

        this.pathSet.remove()
        // @ts-ignore
        delete this.pathSet

        this.shadow.remove()
        // @ts-ignore
        delete this.shadow

        if(this.wire && this.wire.figure==this) {
            delete this.wire.figure
            // @ts-ignore
            delete this.wire
        }
        
        // EventEmitter.destroy()
        this.destroy()
    }

    public serialize() {
        let json: any = {
            color: this._color ,
            segments: []
        }
        for(let seg=this.head; seg; seg=seg.next) {
            json.segments.push({
                x: seg.x ,
                y: seg.y ,
                dir: seg.dir ,
                cross: seg.cross ,
            })
        }
        return json
    }

    public unserialize(json: any) {

        if(json.color) {
            this.color = json.color
        }

        if(json.segments && json.segments.length>1) {
            this.head && this.head.destroy(true)
            delete this.head
            delete this.tail
    
            let prev: Segment|null = null
            for( let i=0; i<json.segments.length; i++ ) {
                let seg = new Segment(json.segments[i], json.segments[i].dir)
                if(i==0)
                    this.head = seg
                else if(i==json.segments.length-1)
                    this.tail = seg
                if(prev) {
                    seg.prev = prev
                    prev.next = seg
                }
                prev = seg
            }
        }

        this.update()
    }
}